// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include <ktest/ktest.h>
#include "krpc/kthread/kthread.h"
#include "krpc/kthread/singleton_on_kthread_once.h"
#include "krpc/kthread/task_control.h"

namespace kthread {
extern TaskControl* g_task_control;
}

namespace {

kthread_once_t g_kthread_once_control;
bool g_kthread_once_started = false;
kutil::atomic<int> g_kthread_once_count(0);

void init_routine() {
    kthread_usleep(2000 * 1000);
    g_kthread_once_count.fetch_add(1, kutil::memory_order_relaxed);
}

void kthread_once_task() {
    kthread_once(&g_kthread_once_control, init_routine);
    //  `init_routine' only be called once.
    ASSERT_EQ(1, g_kthread_once_count.load(kutil::memory_order_relaxed));
}

void* first_kthread_once_task(void*) {
    g_kthread_once_started = true;
    kthread_once_task();
    return NULL;
}


void* other_kthread_once_task(void*) {
    kthread_once_task();
    return NULL;
}

TEST(KthreadOnceTest, once) {
    kthread_t bid;
    ASSERT_EQ(0, kthread_start_background(
        &bid, NULL, first_kthread_once_task, NULL));
    while (!g_kthread_once_started) {
        kthread_usleep(1000);
    }
    ASSERT_NE(nullptr, kthread::g_task_control);
    int concurrency = kthread::g_task_control->concurrency();
    LOG(INFO) << "concurrency: " << concurrency;
    ASSERT_GT(concurrency, 0);
    std::vector<kthread_t> bids(concurrency * 100);
    for (auto& id : bids) {
        ASSERT_EQ(0, kthread_start_background(
            &id, NULL, other_kthread_once_task, NULL));
    }
    kthread_once_task();

    for (auto& id : bids) {
        kthread_join(id, NULL);
    }
    kthread_join(bid, NULL);
}

bool g_kthread_started = false;
kutil::atomic<int> g_kthread_singleton_count(0);

class KthreadSingleton {
public:
    KthreadSingleton() {
        kthread_usleep(2000 * 1000);
        g_kthread_singleton_count.fetch_add(1, kutil::memory_order_relaxed);
    }
};

void get_kthread_singleton() {
    auto instance = kthread::get_leaky_singleton<KthreadSingleton>();
    ASSERT_NE(nullptr, instance);
    // Only one KthreadSingleton instance has been created.
    ASSERT_EQ(1, g_kthread_singleton_count.load(kutil::memory_order_relaxed));
}

void* first_get_kthread_singleton(void*) {
    g_kthread_started = true;
    get_kthread_singleton();
    return NULL;
}


void* get_kthread_singleton(void*) {
    get_kthread_singleton();
    return NULL;
}

// Singleton will definitely not cause deadlock,
// even if constructor of T will hang the kthread.
TEST(KthreadOnceTest, singleton) {
    kthread_t bid;
    ASSERT_EQ(0, kthread_start_background(
        &bid, NULL, first_get_kthread_singleton, NULL));
    while (!g_kthread_started) {
        kthread_usleep(1000);
    }
    ASSERT_NE(nullptr, kthread::g_task_control);
    int concurrency = kthread::g_task_control->concurrency();
    LOG(INFO) << "concurrency: " << concurrency;
    ASSERT_GT(concurrency, 0);
    std::vector<kthread_t> bids(concurrency * 100);
    for (auto& id : bids) {
        ASSERT_EQ(0, kthread_start_background(
            &id, NULL, get_kthread_singleton, NULL));
    }
    get_kthread_singleton();

    for (auto& id : bids) {
        kthread_join(id, NULL);
    }
    kthread_join(bid, NULL);
}

}